﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Lidgren.Network;

using UnitsAndBuilduings;
using Omron.Framework.Networking;

using Microsoft.Xna.Framework;
using Omron.Framework;

using Omron.Helpers;

namespace Omron.Actors
{
    public enum ActionType
    {
        unit,
        building,
        unlock
    }

    class ActionCenter: FatherBuilding, IFullSynchronize, IPushUpdate
    {//factory unit
        public ActionCenter(BuilduingTypeInfo info, string type, Vector2 loc, Faction f)
            : base(info, type, loc)
        {
            if ((info.SubTypeCosts.Length != info.SubTypes.Length) || (info.ActionTimes.Length != info.SubTypes.Length))
                throw new Exception("Unit Subtypes must have the same length as SubTypeCosts and as ActionTimes!!");
            this.Faction = f;
            this.info = info;

            elapsedTime = 0;
            
            queue = new Queue<byte>();
            createMenu();
        }



        void createMenu()
        {

            List<string> oldCues = new List<string>();
            foreach (int cue in queue)
            {
                oldCues.Add(actions[cue]);
            }

            actionTimes = new List<float>();
            actions = new List<string>();
            costs = new List<ResourceData>();
            actionTypes = new List<ActionType>();
            menuConv = new List<byte>();

            for (byte i = 0; i < info.SubTypes.Length; i++)
            {
                if (((info.UnlocksNeeded[i] == "") || (Faction.ResearchUnlocked.Contains(info.UnlocksNeeded[i]))) &&
                    (!Faction.BuildingsUnlocked.Contains(info.SubTypes[i])) && 
                    (!Faction.ResearchUnlocked.Contains(info.SubTypes[i])))
                {
                    actionTimes.Add(info.ActionTimes[i]);
                    actions.Add(info.SubTypes[i]);
                    costs.Add(UnitConverter.CreateResourceData(info.SubTypeCosts[i]));
                    actionTypes.Add((ActionType)info.ActionTypes[i]);
                    menuConv.Add(i);
                }
            }
      
            string[] menuStrs = new string[actions.Count];
            for (byte i = 0; i < menuStrs.Length; i++)
            {
                int ind = menuConv[i];
                menuStrs[i] = info.SubTypes[ind] + "\n" + createResourceDesc(info.SubTypeCosts[ind]);
            }
            menu = new ActorMenu(this, menuStrs);
            menu.CommandInvoked += new MenuItemPressed(menu_ItemClicked);

            queue.Clear(); //VLAD: do we even need this?
            foreach (string str in oldCues)
            {
                if (actions.Contains(str))
                {
                    queue.Enqueue((byte)actions.IndexOf(str));
                }
            }


            updateMenu();
        }

        string createResourceDesc(ResourceCostInfo[] infos)
        {
            string str = "";
            foreach (ResourceCostInfo info in infos)
            {
                str += info.Resource + ": " + info.Cost + "\n";
            }
            return str.TrimEnd(new char[2]{' ', ','});
        }

        void menu_ItemClicked(int itemKey)
        {
            Build(itemKey);
        }

        List<ActionType> actionTypes;
        List<float> actionTimes;
        List<string> actions;
        List<ResourceData> costs;
        List<byte> menuConv;
        BuilduingTypeInfo info;

        public float elapsedTime;
        ActorMenu menu;
        public Queue<byte> queue;

        public bool Build(int itemKey)
        {
            if (Faction.Resources >= costs[itemKey])
            {
                if (actionTypes[itemKey] != ActionType.unit)
                {
                    if (queue.Contains((byte)itemKey))
                    {
                        return false;
                    }
                }
                Faction.Resources -= costs[itemKey];
                queue.Enqueue((byte)itemKey);
                pushEnqueue((byte)itemKey);
                return true;
            }
            return false;
        }

        public override void WriteOutUpdateData(Lidgren.Network.NetOutgoingMessage om)
        {
            base.WriteOutUpdateData(om);


            om.Write(this.elapsedTime);
        }
        public override void ReadInUpdateData(Lidgren.Network.NetIncomingMessage im)
        {
            base.ReadInUpdateData(im);

            elapsedTime = im.ReadFloat();

            updateMenu();
        }

        enum NetworkActionType : byte
        {
            Enqueue,
            Dequeue
        }
        void pushEnqueue(byte data) 
        {
            NetOutgoingMessage update = stage.CreateServerOM();
            if (update != null)
            {
                update.Write((byte)NetworkActionType.Enqueue);
                update.Write(data);
                stage.PushUpdateActor(this, update);
            }
        }
        void pushDequeue()
        {
            NetOutgoingMessage update = stage.CreateServerOM();
            if (update != null)
            {
                update.Write((byte)NetworkActionType.Dequeue);
                stage.PushUpdateActor(this, update);
            }
        }
        public void ReadInPushUpdateData(NetIncomingMessage im)
        {
            switch ((NetworkActionType)im.ReadByte())
            {
                case NetworkActionType.Dequeue:
                    if (queue.Count > 0)
                        queue.Dequeue();
                    break;
                case NetworkActionType.Enqueue:
                    queue.Enqueue(im.ReadByte());
                    break;
            }
            updateMenu();
        }

        public override ActorMenu Menu
        {
            get 
            {
                if (IsComplete)
                {
                    createMenu();
                    return menu;
                }
                return base.Menu;
            }
        }

        float calcBarVal()
        {
            return (float)elapsedTime / actionTimes[queue.Peek()];
        }
        void updateMenu()
        {
            menu.DisplayBar = true;
            if (queue.Count > 0)
                menu.BarValue = calcBarVal();
            else
                menu.DisplayBar = false;


            menu.Info = "building cue: ";
            int counter = 3;
            foreach (byte i in queue.CloneToList())
            {
                counter--;
                if (counter == 0)
                {
                    counter = 3;
                    menu.Info += "\n";
                }
                if (i < 0 || i >= actions.Count) 
                    break;
                menu.Info += actions[i] + ", ";
            }
            menu.Info.TrimEnd(new char[] { ' ', ',' });
        }
        public override void UpdateFast(GameTime gameTime)
        {
            base.UpdateFast(gameTime);

            if (queue.Count != 0)
            {
                SetBaseAnimation(WorkAnimation);

                elapsedTime += (float)gameTime.ElapsedGameTime.TotalSeconds;
                var top = queue.Peek();
                if (elapsedTime >= actionTimes[top])
                {
                    elapsedTime -= actionTimes[top];
                    queue.Dequeue();
                    doAction(top);
                    this.OnUpdateMenu();
                    pushDequeue();
                }
            }
            else if (IsComplete)
            {
                SetBaseAnimation(IdleAnimation);
            }

            updateMenu();
        }

        
        public override void MenuNeedsUpdate() 
        {
            base.MenuNeedsUpdate();
            createMenu();
        }

        void doAction(int action)
        {
            if (actionTypes[action] == ActionType.unit)
            {
                Actor act = UnitConverter.CreateActor(actions[action], Position, this.Faction);
                if (act is FatherUnit)
                {
                    //set target
                    int leastUnits = int.MaxValue;
                    Vector2 target = Vector2.Zero;
                    foreach (Tile loc in stage.TileGrid.GetRadiusCells(GetPosUV(), 2))//change the radius later
                    {
                        if (loc.ProximityActors.Count < leastUnits)
                        {
                            leastUnits = loc.ProximityActors.Count;
                            target = loc.Position;
                        }
                    }
                    Vector2 actOffset = Vector2.Normalize(target - Position) * this.polygon.MaxRadius;
                    (act as FatherUnit).Track(target);
                    act.Position += actOffset;
                }
                else//if it isnt a fatherUnit, its position has not been modified
                    act.Position += Vector2.One * this.polygon.MaxRadius;
                stage.AddActor(act);
            }
            else if (actionTypes[action] == ActionType.building)
            {
                Faction.UnlockBuilding(actions[action]);
                //actions.RemoveAt(action); //create menu does this stuff
                //costs.RemoveAt(action);
                createMenu();
            }
            else
            {
                Faction.UnlockResearch(actions[action]);
                //actions.RemoveAt(action);
                //costs.RemoveAt(action);
                createMenu();
            }
        }
    }
}
